Skip to content

feat: add distribution contract schemas (app, release, rollout_record, announcement, audience)#5

Merged
andrei-hasna merged 4 commits into
mainfrom
feat/distribution-schemas
Jul 7, 2026
Merged

feat: add distribution contract schemas (app, release, rollout_record, announcement, audience)#5
andrei-hasna merged 4 commits into
mainfrom
feat/distribution-schemas

Conversation

@andrei-hasna

Copy link
Copy Markdown
Contributor

What

Implements the contracts foundation of the distribution apps plan (Lane A): five new versioned Zod schemas alongside the existing 21 in src/schemas.ts, following the existing schema/versioning/fixture/conformance patterns.

  • hasna.app.v1 (AppSchema): canonical app identity — stable appId slug, npmName, repoFolder, githubUrl, projectSlug, surfaces { bins[], mcp?, http? }, lifecycle (active|stub|deprecated|archived), releaseChannel (stable|beta|canary|internal).
  • hasna.release.v1 (ReleaseSchema): appId, package, semver version, gitSha, publishedAt, publishPath (skill|ci|backfilled), OPTIONAL changelogRef (deferred refs are legal), evidenceRefs (required unless backfilled).
  • hasna.rollout_record.v1 (RolloutRecordSchema): appId, package, version, machine, action (install|update|rollback|freeze-blocked), result (contract status; freeze-blocked must be blocked/skipped, succeeded install/update requires verifiedBy), verifiedBy { cliVersion?, mcpHealth? }, at.
  • hasna.announcement.v1 (AnnouncementSchema): campaignId, appId?, releaseRef? (kind release), channels[] with per-channel delivery status, audienceRef (kind audience), sentAt.
  • hasna.audience.v1 (AudienceSchema): audienceId, name, definition (tag/attribute/group predicates with all/any matching), consentPolicy, suppressionSyncedAt.

hasna.app_cloud_manifest.v1 disposition

Resolved the competing-identity question: hasna.app_cloud_manifest.v1 is now explicitly NOT an identity schema. Its appId field is tightened to the shared AppIdSchema slug and documented (schema comment + README) as a reference to the canonical hasna.app.v1 document.

Also extends ResourceKindSchema with app, release, rollout, announcement, audience, feedback (additive), registers the five schemas in ContractSchemaRegistry / type maps, and adds valid+invalid fixtures for each with expected issue paths.

Verification

  • bun run typecheck: pass
  • bun test: 115 pass, 6 skip (pre-existing live-pg skips), 0 fail, 499 expect() calls across 10 files
  • bun run conformance: all fixtures ok (valid pass, invalid fail on expected paths)
  • bun run build: pass (27 modules)

Follow-ups

  • @hasna/events gains a typed distribution event catalog binding these schema ids (companion PR in hasna/events).
  • A hasna.feedback.v1 contract is owned by a later lane; the feedback resource kind added here is the anchor for it.

…, announcement, audience)

- hasna.app.v1: canonical app identity (appId slug, npmName, repoFolder,
  githubUrl, projectSlug, surfaces {bins, mcp?, http?}, lifecycle,
  releaseChannel)
- hasna.release.v1: publish receipt with publishPath (skill|ci|backfilled),
  deferred-legal changelogRef, and evidence required unless backfilled
- hasna.rollout_record.v1: per-machine rollout receipt with
  install|update|rollback|freeze-blocked action/result coupling and
  verifiedBy {cliVersion?, mcpHealth?}
- hasna.announcement.v1: campaign receipt with per-channel delivery status
  and audienceRef
- hasna.audience.v1: tag/attribute/group predicate definition with consent
  policy and suppressionSyncedAt
- resolve identity overlap: hasna.app_cloud_manifest.v1 now references
  hasna.app.v1 by AppIdSchema appId and is documented as non-identity
- extend ResourceKindSchema with app/release/rollout/announcement/audience/
  feedback kinds; register schemas, fixtures, and conformance cases
@andrei-hasna

Copy link
Copy Markdown
Contributor Author

OpenLoops worker review for task 1f657b2e-00cd-4d9e-bd90-48a4d3e5be03: not merging yet.

Validated on PR head 2dc7de12cbc014f382a43db4a6407ad3c9f0b36e:

  • bun run verify:release passed: typecheck, bun test before build, examples conformance, build, dist smoke, and pack dry-run.
  • Live PR state: open, non-draft, MERGEABLE / CLEAN, CI verify success.

Merge blockers found during adversarial review:

  1. hasna.app_cloud_manifest.v1 compatibility: PR changes existing appId from z.string().min(1) to the stricter AppIdSchema under the same v1 schema id. Previously valid manifests such as package-like app ids can now fail without a schema-version bump. Please either keep v1 permissive and add canonical app-link checks elsewhere, or use an explicit breaking/versioned path.
  2. hasna.rollout_record.v1 verification strength: successful install/update rollout records only require verifiedBy to exist, but RolloutVerificationSchema allows {} because both fields are optional. Please require at least one concrete verification signal and add a negative test for empty verifiedBy.
  3. Review policy: GitHub still reports latestReviews: [] and empty reviewDecision. This lifecycle requires an eligible non-author review before merge.

Residual reproducibility issue: after verify:release builds ignored dist/, a standalone bun test fails 3 no-cloud/conformance tests because dist/service-contract.js and dist/conformance.js trigger forbidden shared cloud runtime findings. CI passed from a clean checkout, so this may be an existing generated-output sensitivity, but it should be tracked or fixed before release ergonomics are considered clean.

…/contracts 0.4.1) (#6)

* feat(auth): stateless verifiable API-key auth kit

HMAC-signed, self-describing keys (prefix hasna_<app>_) with a scope
grammar (<app>:<action> + wildcards), TTL, and hashed-at-rest records.

- keys.ts: mint/parse/verify signed tokens; sha256 at rest, secret shown once
- scopes.ts: scope grammar + wildcard matcher
- store.ts: DB-backed hashed-record store + revocation list (kit-agnostic client)
- middleware.ts: Express/Hono-agnostic verifyApiKey() + per-request audit hook

* feat(sdk): typed SDK generator from a serve OpenAPI document

generateSdkFromOpenApi() emits a dependency-free fetch client + interfaces
from components.schemas; sends the API key as x-api-key for self_hosted use.

* feat(cli): issue-key issuer + bootstrap key

Mints a scoped API key, persists only the hashed record to the app RDS,
and prints the secret once. --bootstrap mints an <app>:* admin key.

* chore(release): export auth+sdk, document usage, bump to 0.4.1

Wire ./auth and ./sdk package exports and build entrypoints (pinned bun
--root src), clean dist before the release self-scan, and document the
exact serve import path + env vars. Bump @hasna/contracts to 0.4.1.
…s/1a260112-fb07-433d-8381-0269374169fc-22aeea87
@andrei-hasna

Copy link
Copy Markdown
Contributor Author

OpenLoops worker review for task b56f7f2a-d2dc-4140-b477-386898aa2f23: not merging PR #5 yet.\n\nCurrent PR state checked on 2026-07-06:\n- Head: 4ad7447a5b7ef0bfa23ea902597935e03e14031d; base: e4baf61e7825d32ddc6411a5d891f0ab697649dd.\n- GitHub reports open, non-draft, MERGEABLE / CLEAN.\n- CI verify passed: https://github.com/hasna/contracts/actions/runs/28793896926/job/85379267887\n- Local merge-result validation passed: bun run verify:release (153 pass, 7 live Postgres skips, conformance/build/smoke/pack dry-run ok).\n- Secret-like pattern scan over the PR diff found no matches.\n- Independent adversarial reviewer found blockers.\n\nMerge blockers:\n1. Package version collision: PR adds public distribution schemas but leaves @hasna/contracts at 0.4.1 in package.json and CONTRACTS_PACKAGE_VERSION; bun pm view @hasna/contracts version reports 0.4.1 already published. A release from merged main would collide or leave consumers unable to request the new schemas by version.\n2. Semver invariant gap: ReleaseSchema currently accepts invalid semver values with leading-zero numeric identifiers. Verified locally that 01.2.3 and 1.2.3-alpha.01 both pass. This should be tightened before introducing hasna.release.v1 as the canonical publish receipt.\n\nFollow-up Todos task created for the fixes: 27e3270f (Resolve merge blockers for hasna/contracts PR #5 distribution schemas).

@andrei-hasna
andrei-hasna merged commit 594c585 into main Jul 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant